package org.example.hutool;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.lang.Console;
import cn.hutool.http.*;
import cn.hutool.json.JSONUtil;
import org.example.uitls.ResultData;
import org.junit.Test;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.*;

/**
 * Hutool-http针对JDK的HttpUrlConnection做一层封装，简化了HTTPS请求、文件上传、Cookie记忆等操作，使Http请求变得无比简单。
 * Hutool-http的核心集中在两个类：
 * * HttpRequest
 * * HttpResponse
 * 同时针对大部分情境，封装了HttpUtil工具类。
 * <pre>
 * HttpUtil是应对简单场景下Http请求的工具类封装，此工具封装了HttpRequest对象常用操作，可以保证在一个方法之内完成Http请求。
 * 此模块基于JDK的HttpUrlConnection封装完成，完整支持https、代理和文件上传。
 * </pre>
 *
 * <pre>
 * Hutool-http优点
 *  根据URL自动判断是请求HTTP还是HTTPS，不需要单独写多余的代码。
 *  表单数据中有File对象时自动转为multipart/form-data表单，不必单做做操作。
 *  默认情况下Cookie自动记录，比如可以实现模拟登录，即第一次访问登录URL后后续请求就是登录状态。
 *  自动识别304跳转并二次请求
 *  自动识别页面编码，即根据header信息或者页面中的相关标签信息自动识别编码，最大可能避免乱码。
 *  自动识别并解压Gzip格式返回内容
 * </pre>
 *
 * <pre>
 *     本文涉及的http请求后端代码：https://gitee.com/wangmx1993/jpa-transactional/blob/master/src/main/java/com/wmx/controller/RequestController.java
 *
 * </pre>
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2024/2/29 14:21
 */
@RestController
@SuppressWarnings("all")
public class HttpUtilController {

    /**
     * http://localhost:8080/huTool/get1
     * <p>
     * String get(String urlString): 发送get请求
     * 1、最简单的HTTP请求，可以自动通过header等信息判断编码，不区分HTTP和HTTPS
     * <p>
     * String get(String urlString, Charset customCharset)
     * 1、当无法识别页面编码的时候，可以自定义请求页面的编码
     * 2、customCharset 自定义请求字符集，如果字符集获取不到，使用此字符集，比如 CharsetUtil.CHARSET_UTF_8
     */
    @GetMapping("huTool/get1")
    public ResultData testGet1() {
        String url = "https://www.gov.cn/images/jquery.SuperSlide.js";
        String content = cn.hutool.http.HttpUtil.get(url);
        // String content = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8);
        return ResultData.success(content);
    }

    /**
     * http://localhost:8080/huTool/get2
     * <p>
     * String get(String urlString, int timeout)
     * String get(String urlString, Map<String, Object> paramMap)
     * String get(String urlString, Map<String, Object> paramMap, int timeout)
     * * @param urlString 网址
     * * @param paramMap  post表单数据(?后面的查询参数)，会自动做URL编码，拼接在URL中。
     * * @param timeout   超时时长，-1表示默认超时，单位毫秒
     * * @return 返回内容，如果只检查状态码，正常只返回 ""，不正常返回 null
     */
    @GetMapping("huTool/get2")
    public ResultData testGet2() {
        // String url = "http://127.0.0.1:8081/get4?uid=666&pid=777";
        String url = "http://127.0.0.1:8081/get4";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("uid", 888);
        paramMap.put("pid", 999);
        String result1 = cn.hutool.http.HttpUtil.get(url, paramMap);
        return ResultData.success(result1);
    }

    /**
     * http://localhost:8080/huTool/toParams1
     * <p>
     * String toParams(Map<String, ?> paramMap)
     * String toParams(Map<String, ?> paramMap, Charset charset)
     * * 将Map形式的Form表单数据转换为Url参数形式，会自动url编码键和值。
     * * @param paramMap 表单数据，如果key为空（null和""）会被忽略，如果value为null，会被做为空白符（""）
     * * @param charset  编码，null表示不encode键值对
     * * @return url参数 key1=v1;key2=;key3=v3
     *
     * @return
     */
    @GetMapping("huTool/toParams1")
    public ResultData testToParams1() {
        Map<String, Object> paramMap = new HashMap<>(8);
        paramMap.put("uid", 1888);
        paramMap.put("pid", 2999);
        paramMap.put("aid", "7878YT898PL");

        String params = HttpUtil.toParams(paramMap);
        // http://127.0.0.1:8081/get5?uid=1888&pid=2999&aid=7878YT898PL
        String url = "http://127.0.0.1:8081/get5?" + params;
        String result1 = cn.hutool.http.HttpUtil.get(url, paramMap);
        return ResultData.success(result1);
    }

    /**
     * http://localhost:8080/huTool/urlWithForm1
     * <p>
     * String urlWithForm(String url, String queryString, Charset charset, boolean isEncode)
     * String urlWithForm(String url, Map<String, Object> form, Charset charset, boolean isEncodeParams)
     * * 1、将表单数据加到URL中（用于GET表单提交）。会自动处理 ? 、& 等符号
     * * 2、表单的键值对会被url编码，但是url中原参数不会被编码
     * * @param url            URL，原URL中没有后缀?时，追加参数时会自动添加。
     * * @param form           表单数据
     * * @param charset        编码
     * * @param isEncodeParams 是否对键和值做转义处理
     * * @return 合成后的URL
     *
     * @return
     */
    @GetMapping("huTool/urlWithForm1")
    public ResultData testUrlWithForm1() {
        String url = "http://127.0.0.1:8081/get1?token=7858UYT00";
        Map<String, Object> paramMap = new HashMap<>(8);
        paramMap.put("uid", 1888);
        paramMap.put("pid", 2999);
        paramMap.put("aid", "7878YT898PL");

        // http://127.0.0.1:8081/get1?token=7858UYT00&uid=1888&pid=2999&aid=7878YT898PL
        url = HttpUtil.urlWithForm(url, paramMap, Charset.defaultCharset(), true);
        String result1 = cn.hutool.http.HttpUtil.get(url, paramMap);
        return ResultData.success(result1);
    }

    /**
     * Map<String, List<String>> decodeParams(String paramsStr, Charset charset) http://localhost:8080/huTool/decodeParams
     * * 将URL参数解析为Map（也可以解析Post中的键值对参数）
     * * @param paramsStr 参数字符串（或者带参数的Path）
     * * @param charset   字符集
     * * @return 返回的是 LinkedHashMap，参数有序，且当参数重复时，使用 ArrayList 全部解析了。
     * <p>
     * Map<String, String> decodeParamMap(String paramsStr, Charset charset)
     * * 将URL参数解析为Map（也可以解析Post中的键值对参数）
     * * @param paramsStr 参数字符串（或者带参数的Path）
     * * @param charset   字符集
     * * @return 返回的是 HashMap，参数无序，且当参数重复时，只会取最后一个
     *
     * @return
     */
    @GetMapping("huTool/decodeParams")
    public ResultData decodeParams() {
        String url = "http://127.0.0.1:8081/get2?uid=78758&token=7858U&type=1&type=2&type=3";

        Map<String, List<String>> decodeParams = HttpUtil.decodeParams(url, Charset.defaultCharset());
        Map<String, String> decodeParamMap = HttpUtil.decodeParamMap(url, Charset.defaultCharset());

        // {uid=[78758], token=[7858U], type=[1, 2, 3]}
        System.out.println(decodeParams);
        // {uid=78758, type=3, token=7858U}
        System.out.println(decodeParamMap);

        String result1 = cn.hutool.http.HttpUtil.get(url, 60 * 1000);
        return ResultData.success(result1);
    }

    /**
     * String post(String urlString, Map<String, Object> paramMap)
     * String post(String urlString, Map<String, Object> paramMap, int timeout)
     * * 发送post请求
     * * Post请求只需使用Map预先制定form表单项即可。
     * * @param urlString 网址
     * * @param paramMap  post表单数据，会自动转为?后面的查询参数，而不是请求体Body参数。
     * * @param timeout   超时时长，-1表示默认超时，单位毫秒
     */
    @GetMapping("huTool/post1")
    public ResultData testPost1() {
        String url = "http://127.0.0.1:8081//post/save1?type=1&type=2&type=3";
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("uid", "78758");
        paramMap.put("token", "6858U");

        // {"uid":"78758","token":"6858U","type":"1,2,3"}
        String result1 = cn.hutool.http.HttpUtil.post(url, paramMap);

        String url2 = "http://127.0.0.1:8081//post/save1?uid=14541&token=845HUY78&type=1&type=2&type=3";
        String result2 = HttpUtil.post(url2, new HashMap<>(), 60 * 1000);
        // {"uid":"14541","token":"845HUY78","type":"1,2,3"}
        System.out.println(result2);

        return ResultData.success(result1);
    }

    /**
     * http://localhost:8080/huTool/post2
     * <p>
     * String post(String urlString, String body)
     * String post(String urlString, String body, int timeout
     * * 发送post请求<br>
     * * 请求体body参数支持两种类型：
     * * <pre>
     * 	 * 1. 标准参数，例如 a=1&amp;b=2 这种格式
     * 	 * 2. Rest模式，此时body需要传入一个JSON或者XML字符串，Hutool会自动绑定其对应的Content-Type
     * 	 * </pre>
     *
     * @return
     */
    @GetMapping("huTool/post2")
    public ResultData testPost2() {
        String url = "http://127.0.0.1:8081/post/save2?token=784LP565RT77";
        Object[] ids = {"1a", "2D", "3f"};

        String result1 = cn.hutool.http.HttpUtil.post(url, JSONUtil.toJsonStr(ids));
        return ResultData.success(result1);
    }

    /**
     * http://localhost:8080/huTool/post3
     * <p>
     * String post(String urlString, String body)
     * String post(String urlString, String body, int timeout
     * * 发送post请求<br>
     * * 请求体body参数支持两种类型：
     * * <pre>
     * 	 * 1. 标准参数，例如 a=1&amp;b=2 这种格式
     * 	 * 2. Rest模式，此时body需要传入一个JSON或者XML字符串，Hutool会自动绑定其对应的Content-Type
     * 	 * </pre>
     *
     * @return
     */
    @GetMapping("huTool/post3")
    public ResultData testPost3() {
        String url = "http://127.0.0.1:8081/post/save3?token=784LP565RT77";

        List<Map<String, Object>> bodyList = new ArrayList<>();
        Map<String, Object> bodyMap1 = new LinkedHashMap<>();
        Map<String, Object> bodyMap2 = new LinkedHashMap<>();
        Map<String, Object> bodyMap3 = new LinkedHashMap<>();
        bodyMap1.put("id", "101");
        bodyMap1.put("code", "001");
        bodyMap2.put("id", "201");
        bodyMap2.put("code", "002");
        bodyMap3.put("id", "103");
        bodyMap3.put("code", "003");
        bodyList.add(bodyMap1);
        bodyList.add(bodyMap2);
        bodyList.add(bodyMap3);

        // {"mapList":[{"code":"001","id":"101"},{"code":"002","id":"201"},{"code":"003","id":"103"}],"token":"784LP565RT77"}
        String result1 = cn.hutool.http.HttpUtil.post(url, JSONUtil.toJsonStr(bodyList));
        return ResultData.success(JSONUtil.toBean(result1, Map.class));
    }

    /**
     * http://localhost:8080/huTool/post4
     * <p>
     * HttpRequest createGet(String url)
     * 创建Http GET请求对象
     * <p>
     * HttpRequest createPost(String url)
     * * 创建Http POST请求对象
     * * @param url 请求的URL，可以使HTTP或者HTTPS
     * * @return {@link HttpRequest}
     * <p>
     * HttpRequest body(String body) 设置请求体body参数，支持两种类型：
     * * 1. 标准参数，例如 a=1&amp;b=2 这种格式
     * * 2. Rest模式，此时body需要传入一个JSON或者XML字符串，Hutool会自动绑定其对应的Content-Type
     * <p>
     * HttpRequest form(Map<String, Object> formMap) 设置map类型 form 表单数据
     *
     * @return
     */
    @GetMapping("huTool/post4")
    public ResultData testCreatePost() {
        String url = "http://127.0.0.1:8081/post/save4?token=784LP565RT77";
        Long[] ids = {100L, 200L, 300L, 500L, 650L};
        HttpResponse httpResponse = HttpUtil.createPost(url)
                .body(JSONUtil.toJsonStr(ids))
                .execute();

        // 获取响应主体，对方返回的对象(比如Map、List)时会序列化为Json字符串
        // {"ids":[100,200,300,500,650],"token":"784LP565RT77"}
        String result1 = httpResponse.body();
        return ResultData.success(JSONUtil.toBean(result1, Map.class));
    }

    /**
     * http://localhost:8080/huTool/post5
     * <p>
     * HttpRequest createRequest(Method method, String url)
     * * 创建Http请求对象
     * * @param method 方法枚举{@link Method}
     * * @param url    请求的URL，可以使HTTP或者HTTPS
     * * @return {@link HttpRequest}
     *
     * @return
     */
    @GetMapping("huTool/post5")
    public ResultData testCreateRequest() {
        String url = "http://127.0.0.1:8081/post/save5?token=784LP565RT77";
        Map<String, Object> bodyMap = new HashMap<>();
        bodyMap.put("tvId", 1289);
        bodyMap.put("tvName", "小米SU7");
        bodyMap.put("tvPrice", 19828.88F);
        bodyMap.put("dateOfProduction", DateUtil.parse("2023-02-15 15:00:30"));

        HttpResponse httpResponse = HttpUtil.createRequest(Method.POST, url)
                .body(JSONUtil.toJsonStr(bodyMap))
                .execute();

        // 获取响应主体，对方返回的对象(比如Map、List)时会序列化为Json字符串
        // {"tv":{"tvId":1289,"tvPrice":19828.88,"dateOfProduction":"2023-02-15 15:00:30","tvName":"小米SU7"},"token":"784LP565RT77"}}
        String result1 = httpResponse.body();
        return ResultData.success(JSONUtil.toBean(result1, Map.class));
    }

    // HttpUtil中的get和post工具方法都是HttpRequest对象的封装，因此如果想更加灵活操作Http请求，可以直接使用HttpRequest。
    // HttpUtil中的get和post工具方法都是HttpRequest对象的封装，因此如果想更加灵活操作Http请求，可以直接使用HttpRequest。
    // HttpUtil中的get和post工具方法都是HttpRequest对象的封装，因此如果想更加灵活操作Http请求，可以直接使用HttpRequest。

    /**
     * http://localhost:8080/huTool/post6
     *
     * @return
     */
    @GetMapping("huTool/post6")
    public ResultData testPost6() {
        String url = "http://127.0.0.1:8081/post/save6?token=7421212LKJ00";

        List<Map<String, Object>> bodyList = new ArrayList<>();
        Map<String, Object> bodyMap1 = new HashMap<>();
        Map<String, Object> bodyMap2 = new HashMap<>();
        Map<String, Object> bodyMap3 = new HashMap<>();
        bodyMap1.put("tvId", 1289);
        bodyMap1.put("tvName", "小米SU7");
        bodyMap1.put("tvPrice", 19828.88F);
        bodyMap1.put("dateOfProduction", DateUtil.parse("2023-02-1 15:00:30"));
        bodyMap2.put("tvId", 2289);
        bodyMap2.put("tvName", "小米SU8");
        bodyMap2.put("tvPrice", 29828.88F);
        bodyMap2.put("dateOfProduction", DateUtil.parse("2023-02-12 15:00:30"));
        bodyMap3.put("tvId", 3289);
        bodyMap3.put("tvName", "小米SU9");
        bodyMap3.put("tvPrice", 39828.88F);
        bodyMap3.put("dateOfProduction", DateUtil.parse("2023-02-13 15:00:30"));
        bodyList.add(bodyMap1);
        bodyList.add(bodyMap2);
        bodyList.add(bodyMap3);

        // 链式构建请求
        // HttpResponse包含了服务器响应的一些信息，包括响应的内容和响应的头信息.
        HttpResponse httpResponse = HttpRequest.post(url)
                // 请求头信息，多个头信息多次调用此方法即可
                .header(Header.AUTHORIZATION, "Basic c2J5VGVzdF8xMjM6QWJjMTIzNDU2")
                .header(Header.CONTENT_TYPE, "application/json;charset=utf8")
                // 设置map类型表单数据，有body请求体时，查询参数建议直接拼在URL上面。
                // .form()
                // 设置Body请求头参数，转json后传递
                .body(JSONUtil.toJsonStr(bodyList))
                // 超时(毫秒)
                .timeout(60 * 1000)
                // 执行Reuqest请求
                .execute();

        // 请求是否成功，判断依据为：状态码范围在[200,299)内。
        System.out.println("请求是否成功，判断依据为：状态码范围在[200,299)内=" + httpResponse.isOk());
        System.out.println(httpResponse.getCookieStr());
        System.out.println("获取状态码=" + httpResponse.getStatus());
        System.out.println(httpResponse.charset());
        System.out.println(httpResponse.httpVersion());
        System.out.println(httpResponse.toString());

        // 获取响应主体，对方返回的对象(比如Map、List)时会序列化为Json字符串
        String result2 = httpResponse.body();
        return ResultData.success(result2);
    }

    /**
     * http://localhost:8080/huTool/post7
     * 文件上传
     * 文件上传只需将参数中的键指定（默认file，与后台接收文件参数MultipartFile的值保持一致），值设为文件对象即可，对于使用者来说，文件上传与普通表单提交并无区别
     */
    @GetMapping("huTool/post7")
    public ResultData testPost7() {
        // 对应代码：https://gitee.com/wangmx1993/file-server/blob/master/src/main/java/com/wmx/www/controller/UploadFileController.java
        String url = "http://127.0.0.1:8081/fileServer/uploadFile";
        HashMap<String, Object> paramMap = new HashMap<>();
        paramMap.put("userName", "admin");
        paramMap.put("password", "123456");
        // value是文件类型，自动识别为文件上传，多文件上传时，值改为数组就行
        paramMap.put("singleFile", FileUtil.file("C:\\Users\\Think\\Pictures\\Saved Pictures\\25995264077c69eef4d24983bef98785.jpg"));
        String result = HttpUtil.post(url, paramMap);
        return ResultData.success(result);
    }

    /**
     * http://localhost:8080/huTool/post8
     * 文件上传
     * 文件上传只需将参数中的键指定（默认file，与后台接收文件参数MultipartFile的值保持一致），值设为文件对象即可，对于使用者来说，文件上传与普通表单提交并无区别
     */
    @GetMapping("huTool/post8")
    public ResultData testPost8() {
        // 对应代码：https://gitee.com/wangmx1993/file-server/blob/master/src/main/java/com/wmx/www/controller/UploadFileController.java
        String url = "http://127.0.0.1:8081/fileServer/multipleFileUpload";
        File[] files = new File[2];
        files[0] = FileUtil.file("C:\\Users\\Think\\Pictures\\Saved Pictures\\f767d72aac8433ba8ced160636f6ac39.jpg");
        files[1] = FileUtil.file("C:\\Users\\Think\\Pictures\\Saved Pictures\\915de86950b86e098e919b41a90a0c2c.jpg");

        // 表单参数
        HashMap<String, Object> formMap = new HashMap<>();
        formMap.put("userName", "admin");
        formMap.put("password", "123456");
        // value是文件类型，自动识别为文件上传，多文件上传时，值改为数组就行
        formMap.put("multipleFile", files);
        HttpResponse httpResponse = HttpUtil.createPost(url)
                .form(formMap)
                .timeout(60 * 1000)
                .execute();

        String result = httpResponse.body();
        return ResultData.success(result);
    }

    /**
     * http://localhost:8080/huTool/downloadFile1
     * 下载文件
     * 因为Hutool-http机制问题，请求页面返回结果是一次性解析为byte[]的，如果请求URL返回结果太大（比如文件下载），那内存会爆掉，
     * 因此针对文件下载HttpUtil单独做了封装。文件下载在面对大文件时采用流的方式读写，内存中只是保留一定量的缓存，然后分块写入硬盘，
     * 因此大文件情况下不会对内存有压力。
     * <p>
     * long downloadFile(String url, File destFile)
     * long downloadFile(String url, File destFile, int timeout)
     * * 下载远程文件
     * * @param url      请求的url
     * * @param destFile 目标文件或目录，当为目录时，取URL中的文件名，取不到使用编码后的URL做为文件名
     * * @param timeout  超时，单位毫秒，-1表示默认超时
     * * @return 文件大小
     */
    @GetMapping("huTool/downloadFile1")
    public ResultData testDownloadFile1() {
        String fileUrl = "https://d.ifengimg.com/w1125_q90_webp/x0.ifengimg.com/ucms/2024_12/A136FBFA8F26BAADF85848CEF249C430546868F1_size133_w1280_h884.jpg";
        //将文件下载后保存在桌面，返回结果为下载文件大小
        File destDir = FileSystemView.getFileSystemView().getHomeDirectory();
        String fileName = FileUtil.getName(fileUrl);
        long downloadFileSize = cn.hutool.http.HttpUtil.downloadFile(fileUrl, destDir);
        return ResultData.success("文件下载位置：" + destDir + File.separator + fileName + "，文件大小：" + FileUtil.readableFileSize(downloadFileSize));
    }

    /**
     * http://localhost:8080/huTool/downloadFile2
     * 如果想感知下载进度，还可以使用另一个重载方法回调感知下载进度：
     * long downloadFile(String url, File destFile, StreamProgress streamProgress)
     * long downloadFile(String url, File destFile, int timeout, StreamProgress streamProgress)
     * * 下载远程文件
     * * @param url            请求的url
     * * @param destFile       目标文件或目录，当为目录时，取URL中的文件名，取不到使用编码后的URL做为文件名
     * * @param timeout        超时，单位毫秒，-1表示默认超时
     * * @param streamProgress 进度条
     */
    @GetMapping("huTool/downloadFile2")
    public ResultData testDownloadFile2() {
        String fileUrl = "https://jvod.300hu.com/vod/product/31405aa0-b6bf-40d5-9122-fe453d04e982/1945c90cb780434eabe7f2541177ece4.mp4";
        String fileName = FileUtil.getName(fileUrl);
        File destDir = FileSystemView.getFileSystemView().getHomeDirectory();
        // 带进度显示的文件下载
        long downloadFileSize = HttpUtil.downloadFile(fileUrl, destDir, new StreamProgress() {
            @Override
            public void start() {
                Console.log("开始下载。。。。");
            }

            @Override
            public void progress(long total, long progressSize) {
                Console.log("总共大小：{} 当前下载：{}", FileUtil.readableFileSize(total), FileUtil.readableFileSize(progressSize));
            }

            @Override
            public void finish() {
                Console.log("下载完成！");
            }
        });
        return ResultData.success("文件下载位置：" + destDir + File.separator + fileName + "，文件大小：" + FileUtil.readableFileSize(downloadFileSize));
    }

    /**
     * long download(String url, OutputStream out, boolean isCloseOut)
     * long download(String url, OutputStream out, boolean isCloseOut, StreamProgress streamProgress
     * * 下载远程文件
     * * @param url        请求的url
     * * @param out        将下载内容写到输出流中 {@link OutputStream}
     * * @param isCloseOut 是否关闭输出流
     * * @param streamProgress 进度条
     * * @return 文件大小
     */
    @Test
    public void testDownloadFile3() {
        // 对应代码：https://gitee.com/wangmx1993/file-server/blob/master/src/main/java/com/wmx/www/controller/DownloadFileController.java
        String url = "xx";
        // HttpUtil.download()
    }

}
